/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License.  You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.apache.solr.core;

import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Properties;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.SynchronousQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;

import javax.xml.parsers.ParserConfigurationException;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerException;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMResult;
import javax.xml.transform.dom.DOMSource;
import javax.xml.xpath.XPath;
import javax.xml.xpath.XPathConstants;
import javax.xml.xpath.XPathExpressionException;

import org.apache.commons.io.IOUtils;
import org.apache.solr.cloud.CloudDescriptor;
import org.apache.solr.cloud.CurrentCoreDescriptorProvider;
import org.apache.solr.cloud.SolrZkServer;
import org.apache.solr.cloud.ZkController;
import org.apache.solr.cloud.ZkSolrResourceLoader;
import org.apache.solr.common.SolrException;
import org.apache.solr.common.SolrException.ErrorCode;
import org.apache.solr.common.cloud.ZkStateReader;
import org.apache.solr.common.cloud.ZooKeeperException;
import org.apache.solr.common.util.ExecutorUtil;
import org.apache.solr.core.SolrXMLSerializer.SolrCoreXMLDef;
import org.apache.solr.core.SolrXMLSerializer.SolrXMLDef;
import org.apache.solr.handler.admin.CollectionsHandler;
import org.apache.solr.handler.admin.CoreAdminHandler;
import org.apache.solr.handler.component.HttpShardHandlerFactory;
import org.apache.solr.handler.component.ShardHandlerFactory;
import org.apache.solr.logging.ListenerConfig;
import org.apache.solr.logging.LogWatcher;
import org.apache.solr.logging.jul.JulWatcher;
import org.apache.solr.schema.IndexSchema;
import org.apache.solr.update.SolrCoreState;
import org.apache.solr.util.DOMUtil;
import org.apache.solr.util.DefaultSolrThreadFactory;
import org.apache.solr.util.FileUtils;
import org.apache.solr.util.SystemIdResolver;
import org.apache.zookeeper.KeeperException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.slf4j.impl.StaticLoggerBinder;
import org.w3c.dom.Document;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;

/**
 * 
 * @since solr 1.3
 */
public class CoreContainer {
	private static final String LEADER_VOTE_WAIT = "180000"; // 3 minutes
	private static final String DEFAULT_HOST_CONTEXT = "solr";
	private static final String DEFAULT_HOST_PORT = "8983";
	private static final int DEFAULT_ZK_CLIENT_TIMEOUT = 15000;
	public static final String DEFAULT_DEFAULT_CORE_NAME = "collection1";
	private static final boolean DEFAULT_SHARE_SCHEMA = false;

	protected static Logger log = LoggerFactory.getLogger(CoreContainer.class);

	// solr.xml node constants
	private static final String CORE_NAME = "name";
	private static final String CORE_CONFIG = "config";
	private static final String CORE_INSTDIR = "instanceDir";
	private static final String CORE_DATADIR = "dataDir";
	private static final String CORE_SCHEMA = "schema";
	private static final String CORE_SHARD = "shard";
	private static final String CORE_COLLECTION = "collection";
	private static final String CORE_ROLES = "roles";
	private static final String CORE_PROPERTIES = "properties";

	protected final Map<String, SolrCore> cores = new LinkedHashMap<String, SolrCore>();

	protected final Map<String, Exception> coreInitFailures = Collections
			.synchronizedMap(new LinkedHashMap<String, Exception>());

	protected boolean persistent = false;
	protected String adminPath = null;
	protected String managementPath = null;
	protected String hostPort;
	protected String hostContext;
	protected String host;
	protected CoreAdminHandler coreAdminHandler = null;
	protected CollectionsHandler collectionsHandler = null;
	protected File configFile = null;
	protected String libDir = null;
	protected ClassLoader libLoader = null;
	protected SolrResourceLoader loader = null;
	protected Properties containerProperties;
	protected Map<String, IndexSchema> indexSchemaCache;
	protected String adminHandler;
	protected boolean shareSchema;
	protected Integer zkClientTimeout;
	protected String solrHome;
	protected String defaultCoreName = null;
	private SolrXMLSerializer solrXMLSerializer = new SolrXMLSerializer();
	private ZkController zkController;
	private SolrZkServer zkServer;
	private ShardHandlerFactory shardHandlerFactory;
	protected LogWatcher logging = null;
	private String zkHost;
	private Map<SolrCore, String> coreToOrigName = new ConcurrentHashMap<SolrCore, String>();
	private String leaderVoteWait;

	{
		log.info("New CoreContainer " + System.identityHashCode(this));
	}

	/**
	 * Deprecated
	 * 
	 * @deprecated use the single arg constructure with locateSolrHome()
	 * @see SolrResourceLoader#locateSolrHome
	 */
	@Deprecated
	public CoreContainer() {
		this(SolrResourceLoader.locateSolrHome());
	}

	/**
	 * Initalize CoreContainer directly from the constructor
	 */
	public CoreContainer(String dir, File configFile)
			throws ParserConfigurationException, IOException, SAXException {
		this(dir);
		this.load(dir, configFile);
	}

	/**
	 * Minimal CoreContainer constructor.
	 * 
	 * @param loader
	 *            the CoreContainer resource loader
	 */
	public CoreContainer(SolrResourceLoader loader) {
		this(loader.getInstanceDir());
		this.loader = loader;
	}

	public CoreContainer(String solrHome) {
		this.solrHome = solrHome;
	}

	protected void initZooKeeper(String zkHost, int zkClientTimeout) {
		// if zkHost sys property is not set, we are not using ZooKeeper
		String zookeeperHost;
		if (zkHost == null) {
			zookeeperHost = System.getProperty("zkHost");
		} else {
			zookeeperHost = zkHost;
		}

		String zkRun = System.getProperty("zkRun");

		if (zkRun == null && zookeeperHost == null)
			return; // not in zk mode

		// zookeeper in quorum mode currently causes a failure when trying to
		// register log4j mbeans. See SOLR-2369
		// TODO: remove after updating to an slf4j based zookeeper
		System.setProperty("zookeeper.jmx.log4j.disable", "true");

		if (zkRun != null) {
			String zkDataHome = System.getProperty("zkServerDataDir", solrHome
					+ "zoo_data");
			String zkConfHome = System.getProperty("zkServerConfDir", solrHome);
			zkServer = new SolrZkServer(zkRun, zookeeperHost, zkDataHome,
					zkConfHome, hostPort);
			zkServer.parseConfig();
			zkServer.start();

			// set client from server config if not already set
			if (zookeeperHost == null) {
				zookeeperHost = zkServer.getClientString();
			}
		}

		int zkClientConnectTimeout = 15000;

		if (zookeeperHost != null) {
			// we are ZooKeeper enabled
			try {
				// If this is an ensemble, allow for a long connect time for
				// other servers to come up
				if (zkRun != null && zkServer.getServers().size() > 1) {
					zkClientConnectTimeout = 24 * 60 * 60 * 1000; // 1 day for
																	// embedded
																	// ensemble
					log.info("Zookeeper client=" + zookeeperHost
							+ "  Waiting for a quorum.");
				} else {
					log.info("Zookeeper client=" + zookeeperHost);
				}
				zkController = new ZkController(this, zookeeperHost,
						zkClientTimeout, zkClientConnectTimeout, host,
						hostPort, hostContext, leaderVoteWait,
						new CurrentCoreDescriptorProvider() {

							@Override
							public List<CoreDescriptor> getCurrentDescriptors() {
								List<CoreDescriptor> descriptors = new ArrayList<CoreDescriptor>(
										getCoreNames().size());
								for (SolrCore core : getCores()) {
									descriptors.add(core.getCoreDescriptor());
								}
								return descriptors;
							}
						});

				String confDir = System.getProperty("bootstrap_confdir");
				boolean boostrapConf = Boolean.getBoolean("bootstrap_conf");

				if (zkRun != null && zkServer.getServers().size() > 1
						&& confDir == null && boostrapConf == false) {
					// we are part of an ensemble and we are not uploading the
					// config - pause to give the config time
					// to get up
					Thread.sleep(10000);
				}

				if (confDir != null) {
					File dir = new File(confDir);
					if (!dir.isDirectory()) {
						throw new IllegalArgumentException(
								"bootstrap_confdir must be a directory of configuration files");
					}
					String confName = System.getProperty(
							ZkController.COLLECTION_PARAM_PREFIX
									+ ZkController.CONFIGNAME_PROP,
							"configuration1");
					zkController.uploadConfigDir(dir, confName);
				}

				if (boostrapConf) {
					ZkController.bootstrapConf(zkController.getZkClient(), cfg,
							solrHome);
				}

			} catch (InterruptedException e) {
				// Restore the interrupted status
				Thread.currentThread().interrupt();
				log.error("", e);
				throw new ZooKeeperException(
						SolrException.ErrorCode.SERVER_ERROR, "", e);
			} catch (TimeoutException e) {
				log.error("Could not connect to ZooKeeper", e);
				throw new ZooKeeperException(
						SolrException.ErrorCode.SERVER_ERROR, "", e);
			} catch (IOException e) {
				log.error("", e);
				throw new ZooKeeperException(
						SolrException.ErrorCode.SERVER_ERROR, "", e);
			} catch (KeeperException e) {
				log.error("", e);
				throw new ZooKeeperException(
						SolrException.ErrorCode.SERVER_ERROR, "", e);
			}
		}

	}

	public Properties getContainerProperties() {
		return containerProperties;
	}

	// Helper class to initialize the CoreContainer
	public static class Initializer {
		protected String containerConfigFilename = null; // normally "solr.xml"
		protected String dataDir = null; // override datadir for single core
											// mode

		// core container instantiation
		public CoreContainer initialize() throws IOException,
				ParserConfigurationException, SAXException {
			CoreContainer cores = null;
			String solrHome = SolrResourceLoader.locateSolrHome();
			File fconf = new File(solrHome,
					containerConfigFilename == null ? "solr.xml"
							: containerConfigFilename);
			log.info("looking for solr.xml: " + fconf.getAbsolutePath());
			cores = new CoreContainer(solrHome);

			if (fconf.exists()) {
				cores.load(solrHome, fconf);
			} else {
				log.info("no solr.xml file found - using default");
				cores.load(solrHome, new InputSource(new ByteArrayInputStream(
						DEF_SOLR_XML.getBytes("UTF-8"))));
				cores.configFile = fconf;
			}

			containerConfigFilename = cores.getConfigFile().getName();

			return cores;
		}
	}

	static Properties getCoreProps(String instanceDir, String file,
			Properties defaults) {
		if (file == null)
			file = "conf" + File.separator + "solrcore.properties";
		File corePropsFile = new File(file);
		if (!corePropsFile.isAbsolute()) {
			corePropsFile = new File(instanceDir, file);
		}
		Properties p = defaults;
		if (corePropsFile.exists() && corePropsFile.isFile()) {
			p = new Properties(defaults);
			InputStream is = null;
			try {
				is = new FileInputStream(corePropsFile);
				p.load(is);
			} catch (IOException e) {
				log.warn("Error loading properties ", e);
			} finally {
				IOUtils.closeQuietly(is);
			}
		}
		return p;
	}

	// -------------------------------------------------------------------
	// Initialization / Cleanup
	// -------------------------------------------------------------------

	/**
	 * Load a config file listing the available solr cores.
	 * 
	 * @param dir
	 *            the home directory of all resources.
	 * @param configFile
	 *            the configuration file
	 */
	public void load(String dir, File configFile)
			throws ParserConfigurationException, IOException, SAXException {
		this.configFile = configFile;
		this.load(dir, new InputSource(configFile.toURI().toASCIIString()));
	}

	/**
	 * Load a config file listing the available solr cores.
	 * 
	 * @param dir
	 *            the home directory of all resources.
	 * @param cfgis
	 *            the configuration file InputStream
	 */
	public void load(String dir, InputSource cfgis)
			throws ParserConfigurationException, IOException, SAXException {

		if (null == dir) {
			// don't rely on SolrResourceLoader(), determine explicitly first
			dir = SolrResourceLoader.locateSolrHome();
		}
		log.info("Loading CoreContainer using Solr Home: '{}'", dir);

		this.loader = new SolrResourceLoader(dir);
		solrHome = loader.getInstanceDir();

		Config cfg = new Config(loader, null, cfgis, null, false);

		// keep orig config for persist to consult
		try {
			this.cfg = new Config(loader, null, copyDoc(cfg.getDocument()));
		} catch (TransformerException e) {
			throw new SolrException(ErrorCode.SERVER_ERROR, "", e);
		}

		cfg.substituteProperties();

		// Initialize Logging
		if (cfg.getBool("solr/logging/@enabled", true)) {
			String slf4jImpl = null;
			String fname = cfg.get("solr/logging/watcher/@class", null);
			try {
				slf4jImpl = StaticLoggerBinder.getSingleton()
						.getLoggerFactoryClassStr();
				if (fname == null) {
					if (slf4jImpl.indexOf("Log4j") > 0) {
						log.warn("Log watching is not yet implemented for log4j");
					} else if (slf4jImpl.indexOf("JDK") > 0) {
						fname = "JUL";
					}
				}
			} catch (Throwable ex) {
				log.warn("Unable to read SLF4J version.  LogWatcher will be disabled: "
						+ ex);
			}

			// Now load the framework
			if (fname != null) {
				if ("JUL".equalsIgnoreCase(fname)) {
					logging = new JulWatcher(slf4jImpl);
				}
				// else if( "Log4j".equals(fname) ) {
				// logging = new Log4jWatcher(slf4jImpl);
				// }
				else {
					try {
						logging = loader.newInstance(fname, LogWatcher.class);
					} catch (Throwable e) {
						log.warn("Unable to load LogWatcher", e);
					}
				}

				if (logging != null) {
					ListenerConfig v = new ListenerConfig();
					v.size = cfg.getInt("solr/logging/watcher/@size", 50);
					v.threshold = cfg.get("solr/logging/watcher/@threshold",
							null);
					if (v.size > 0) {
						log.info("Registering Log Listener");
						logging.registerListener(v, this);
					}
				}
			}
		}

		String dcoreName = cfg.get("solr/cores/@defaultCoreName", null);
		if (dcoreName != null && !dcoreName.isEmpty()) {
			defaultCoreName = dcoreName;
		}
		persistent = cfg.getBool("solr/@persistent", false);
		libDir = cfg.get("solr/@sharedLib", null);
		zkHost = cfg.get("solr/@zkHost", null);
		adminPath = cfg.get("solr/cores/@adminPath", null);
		shareSchema = cfg.getBool("solr/cores/@shareSchema",
				DEFAULT_SHARE_SCHEMA);
		zkClientTimeout = cfg.getInt("solr/cores/@zkClientTimeout",
				DEFAULT_ZK_CLIENT_TIMEOUT);

		hostPort = cfg.get("solr/cores/@hostPort", DEFAULT_HOST_PORT);

		hostContext = cfg.get("solr/cores/@hostContext", DEFAULT_HOST_CONTEXT);
		host = cfg.get("solr/cores/@host", null);

		leaderVoteWait = cfg
				.get("solr/cores/@leaderVoteWait", LEADER_VOTE_WAIT);

		if (shareSchema) {
			indexSchemaCache = new ConcurrentHashMap<String, IndexSchema>();
		}
		adminHandler = cfg.get("solr/cores/@adminHandler", null);
		managementPath = cfg.get("solr/cores/@managementPath", null);

		zkClientTimeout = Integer.parseInt(System.getProperty(
				"zkClientTimeout", Integer.toString(zkClientTimeout)));
		initZooKeeper(zkHost, zkClientTimeout);

		if (libDir != null) {
			File f = FileUtils.resolvePath(new File(dir), libDir);
			log.info("loading shared library: " + f.getAbsolutePath());
			libLoader = SolrResourceLoader.createClassLoader(f, null);
		}

		if (adminPath != null) {
			if (adminHandler == null) {
				coreAdminHandler = new CoreAdminHandler(this);
			} else {
				coreAdminHandler = this.createMultiCoreHandler(adminHandler);
			}
		}

		collectionsHandler = new CollectionsHandler(this);

		try {
			containerProperties = readProperties(cfg, ((NodeList) cfg.evaluate(
					DEFAULT_HOST_CONTEXT, XPathConstants.NODESET)).item(0));
		} catch (Throwable e) {
			SolrException.log(log, null, e);
		}

		NodeList nodes = (NodeList) cfg.evaluate("solr/cores/core",
				XPathConstants.NODESET);

		for (int i = 0; i < nodes.getLength(); i++) {
			Node node = nodes.item(i);
			try {
				String rawName = DOMUtil.getAttr(node, CORE_NAME, null);
				if (null == rawName) {
					throw new SolrException(
							SolrException.ErrorCode.SERVER_ERROR,
							"Each core in solr.xml must have a 'name'");
				}
				String name = rawName;
				CoreDescriptor p = new CoreDescriptor(this, name,
						DOMUtil.getAttr(node, CORE_INSTDIR, null));

				// deal with optional settings
				String opt = DOMUtil.getAttr(node, CORE_CONFIG, null);

				if (opt != null) {
					p.setConfigName(opt);
				}
				opt = DOMUtil.getAttr(node, CORE_SCHEMA, null);
				if (opt != null) {
					p.setSchemaName(opt);
				}
				if (zkController != null) {
					opt = DOMUtil.getAttr(node, CORE_SHARD, null);
					if (opt != null && opt.length() > 0) {
						p.getCloudDescriptor().setShardId(opt);
					}
					opt = DOMUtil.getAttr(node, CORE_COLLECTION, null);
					if (opt != null) {
						p.getCloudDescriptor().setCollectionName(opt);
					}
					opt = DOMUtil.getAttr(node, CORE_ROLES, null);
					if (opt != null) {
						p.getCloudDescriptor().setRoles(opt);
					}
				}
				opt = DOMUtil.getAttr(node, CORE_PROPERTIES, null);
				if (opt != null) {
					p.setPropertiesName(opt);
				}
				opt = DOMUtil.getAttr(node, CORE_DATADIR, null);
				if (opt != null) {
					p.setDataDir(opt);
				}

				p.setCoreProperties(readProperties(cfg, node));

				SolrCore core = create(p);
				register(name, core, false);

				// track original names
				coreToOrigName.put(core, rawName);
			} catch (Throwable ex) {
				SolrException.log(log, null, ex);
			}
		}
	}

	private Document copyDoc(Document document) throws TransformerException {
		TransformerFactory tfactory = TransformerFactory.newInstance();
		Transformer tx = tfactory.newTransformer();
		DOMSource source = new DOMSource(document);
		DOMResult result = new DOMResult();
		tx.transform(source, result);
		return (Document) result.getNode();
	}

	private Properties readProperties(Config cfg, Node node)
			throws XPathExpressionException {
		XPath xpath = cfg.getXPath();
		NodeList props = (NodeList) xpath.evaluate("property", node,
				XPathConstants.NODESET);
		Properties properties = new Properties();
		for (int i = 0; i < props.getLength(); i++) {
			Node prop = props.item(i);
			properties.setProperty(DOMUtil.getAttr(prop, "name"),
					DOMUtil.getAttr(prop, "value"));
		}
		return properties;
	}

	private volatile boolean isShutDown = false;

	private volatile Config cfg;

	public boolean isShutDown() {
		return isShutDown;
	}

	/**
	 * Stops all cores.
	 */
	public void shutdown() {
		log.info("Shutting down CoreContainer instance="
				+ System.identityHashCode(this));
		isShutDown = true;

		if (isZooKeeperAware()) {
			cancelCoreRecoveries();
		}
		try {
			synchronized (cores) {

				for (SolrCore core : cores.values()) {
					try {
						core.close();
					} catch (Throwable t) {
						SolrException.log(log, "Error shutting down core", t);
					}
				}
				cores.clear();
			}
		} finally {
			if (shardHandlerFactory != null) {
				shardHandlerFactory.close();
			}

			// we want to close zk stuff last
			if (zkController != null) {
				zkController.close();
			}
			if (zkServer != null) {
				zkServer.stop();
			}

		}
	}

	public void cancelCoreRecoveries() {
		ArrayList<SolrCoreState> coreStates = new ArrayList<SolrCoreState>();
		synchronized (cores) {
			for (SolrCore core : cores.values()) {
				coreStates.add(core.getUpdateHandler().getSolrCoreState());
			}
		}

		// we must cancel without holding the cores sync
		// make sure we wait for any recoveries to stop
		for (SolrCoreState coreState : coreStates) {
			try {
				coreState.cancelRecovery();
			} catch (Throwable t) {
				SolrException.log(log, "Error canceling recovery for core", t);
			}
		}
	}

	@Override
	protected void finalize() throws Throwable {
		try {
			if (!isShutDown) {
				log.error("CoreContainer was not shutdown prior to finalize(), indicates a bug -- POSSIBLE RESOURCE LEAK!!!  instance="
						+ System.identityHashCode(this));
			}
		} finally {
			super.finalize();
		}
	}

	/**
	 * Registers a SolrCore descriptor in the registry using the specified name.
	 * If returnPrevNotClosed==false, the old core, if different, is closed. if
	 * true, it is returned w/o closing the core
	 * 
	 * @return a previous core having the same name if it existed
	 */
	public SolrCore register(String name, SolrCore core,
			boolean returnPrevNotClosed) {
		if (core == null) {
			throw new RuntimeException("Can not register a null core.");
		}
		if (name == null || name.indexOf('/') >= 0 || name.indexOf('\\') >= 0) {
			throw new RuntimeException("Invalid core name: " + name);
		}

		if (zkController != null) {
			// this happens before we can receive requests
			try {
				zkController.preRegister(core.getCoreDescriptor());
			} catch (KeeperException e) {
				log.error("", e);
				throw new ZooKeeperException(
						SolrException.ErrorCode.SERVER_ERROR, "", e);
			} catch (InterruptedException e) {
				Thread.currentThread().interrupt();
				log.error("", e);
				throw new ZooKeeperException(
						SolrException.ErrorCode.SERVER_ERROR, "", e);
			}
		}

		SolrCore old = null;
		synchronized (cores) {
			if (isShutDown) {
				core.close();
				throw new IllegalStateException(
						"This CoreContainer has been shutdown");
			}
			old = cores.put(name, core);
			coreInitFailures.remove(name);
			/*
			 * set both the name of the descriptor and the name of the core,
			 * since the descriptors name is used for persisting.
			 */
			core.setName(name);
			core.getCoreDescriptor().name = name;
		}

		if (old == null || old == core) {
			log.info("registering core: " + name);
			registerInZk(core);
			return null;
		} else {
			log.info("replacing core: " + name);
			if (!returnPrevNotClosed) {
				old.close();
			}
			registerInZk(core);
			return old;
		}
	}

	private void registerInZk(SolrCore core) {
		if (zkController != null) {
			try {
				zkController.register(core.getName(), core.getCoreDescriptor());
			} catch (InterruptedException e) {
				// Restore the interrupted status
				Thread.currentThread().interrupt();
				SolrException.log(log, "", e);
				throw new ZooKeeperException(
						SolrException.ErrorCode.SERVER_ERROR, "", e);
			} catch (Exception e) {
				// if register fails, this is really bad - close the
				// zkController to
				// minimize any damage we can cause
				try {
					zkController.publish(core.getCoreDescriptor(),
							ZkStateReader.DOWN);
				} catch (KeeperException e1) {
					log.error("", e);
					throw new ZooKeeperException(
							SolrException.ErrorCode.SERVER_ERROR, "", e);
				} catch (InterruptedException e1) {
					Thread.currentThread().interrupt();
					log.error("", e);
					throw new ZooKeeperException(
							SolrException.ErrorCode.SERVER_ERROR, "", e);
				}
				SolrException.log(log, "", e);
				throw new ZooKeeperException(
						SolrException.ErrorCode.SERVER_ERROR, "", e);
			}
		}
	}

	/**
	 * Registers a SolrCore descriptor in the registry using the core's name. If
	 * returnPrev==false, the old core, if different, is closed.
	 * 
	 * @return a previous core having the same name if it existed and
	 *         returnPrev==true
	 */
	public SolrCore register(SolrCore core, boolean returnPrev) {
		return register(core.getName(), core, returnPrev);
	}

	/**
	 * Creates a new core based on a descriptor but does not register it.
	 * 
	 * @param dcore
	 *            a core descriptor
	 * @return the newly created core
	 */
	public SolrCore create(CoreDescriptor dcore)
			throws ParserConfigurationException, IOException, SAXException {

		// :TODO: would be really nice if this method wrapped any underlying
		// errors and only threw SolrException

		final String name = dcore.getName();
		Exception failure = null;

		try {
			// Make the instanceDir relative to the cores instanceDir if not
			// absolute
			File idir = new File(dcore.getInstanceDir());
			if (!idir.isAbsolute()) {
				idir = new File(solrHome, dcore.getInstanceDir());
			}
			String instanceDir = idir.getPath();
			log.info("Creating SolrCore '{}' using instanceDir: {}",
					dcore.getName(), instanceDir);
			// Initialize the solr config
			SolrResourceLoader solrLoader = null;

			SolrConfig config = null;
			String zkConfigName = null;
			if (zkController == null) {
				solrLoader = new SolrResourceLoader(instanceDir, libLoader,
						getCoreProps(instanceDir, dcore.getPropertiesName(),
								dcore.getCoreProperties()));
				config = new SolrConfig(solrLoader, dcore.getConfigName(), null);
			} else {
				try {
					String collection = dcore.getCloudDescriptor()
							.getCollectionName();
					zkController.createCollectionZkNode(dcore
							.getCloudDescriptor());

					zkConfigName = zkController.readConfigName(collection);
					if (zkConfigName == null) {
						log.error("Could not find config name for collection:"
								+ collection);
						throw new ZooKeeperException(
								SolrException.ErrorCode.SERVER_ERROR,
								"Could not find config name for collection:"
										+ collection);
					}
					solrLoader = new ZkSolrResourceLoader(instanceDir,
							zkConfigName, libLoader, getCoreProps(instanceDir,
									dcore.getPropertiesName(),
									dcore.getCoreProperties()), zkController);
					config = getSolrConfigFromZk(zkConfigName,
							dcore.getConfigName(), solrLoader);
				} catch (KeeperException e) {
					log.error("", e);
					throw new ZooKeeperException(
							SolrException.ErrorCode.SERVER_ERROR, "", e);
				} catch (InterruptedException e) {
					// Restore the interrupted status
					Thread.currentThread().interrupt();
					log.error("", e);
					throw new ZooKeeperException(
							SolrException.ErrorCode.SERVER_ERROR, "", e);
				}
			}

			IndexSchema schema = null;
			if (indexSchemaCache != null) {
				if (zkController == null) {
					File schemaFile = new File(dcore.getSchemaName());
					if (!schemaFile.isAbsolute()) {
						schemaFile = new File(solrLoader.getInstanceDir()
								+ "conf" + File.separator
								+ dcore.getSchemaName());
					}
					if (schemaFile.exists()) {
						String key = schemaFile.getAbsolutePath()
								+ ":"
								+ new SimpleDateFormat("yyyyMMddHHmmss",
										Locale.ROOT).format(new Date(schemaFile
										.lastModified()));
						schema = indexSchemaCache.get(key);
						if (schema == null) {
							log.info("creating new schema object for core: "
									+ dcore.name);
							schema = new IndexSchema(config,
									dcore.getSchemaName(), null);
							indexSchemaCache.put(key, schema);
						} else {
							log.info("re-using schema object for core: "
									+ dcore.name);
						}
					}
				} else {
					// TODO: handle caching from ZooKeeper - perhaps using
					// ZooKeepers versioning
					// Don't like this cache though - how does it empty as last
					// modified changes?
				}
			}
			if (schema == null) {
				if (zkController != null) {
					try {
						schema = getSchemaFromZk(zkConfigName,
								dcore.getSchemaName(), config, solrLoader);
					} catch (KeeperException e) {
						log.error("", e);
						throw new ZooKeeperException(
								SolrException.ErrorCode.SERVER_ERROR, "", e);
					} catch (InterruptedException e) {
						// Restore the interrupted status
						Thread.currentThread().interrupt();
						log.error("", e);
						throw new ZooKeeperException(
								SolrException.ErrorCode.SERVER_ERROR, "", e);
					}
				} else {
					schema = new IndexSchema(config, dcore.getSchemaName(),
							null);
				}
			}

			SolrCore core = new SolrCore(dcore.getName(), null, config, schema,
					dcore);

			if (zkController == null
					&& core.getUpdateHandler().getUpdateLog() != null) {
				// always kick off recovery if we are in standalone mode.
				core.getUpdateHandler().getUpdateLog().recoverFromLog();
			}

			return core;

			// :TODO: Java7...
			// http://docs.oracle.com/javase/7/docs/technotes/guides/language/catch-multiple.html
		} catch (ParserConfigurationException e1) {
			failure = e1;
			throw e1;
		} catch (IOException e2) {
			failure = e2;
			throw e2;
		} catch (SAXException e3) {
			failure = e3;
			throw e3;
		} catch (RuntimeException e4) {
			failure = e4;
			throw e4;
		} finally {
			if (null != failure) {
				log.error("Unable to create core: " + name, failure);
			}
			synchronized (coreInitFailures) {
				// remove first so insertion order is updated and newest is last
				coreInitFailures.remove(name);
				if (null != failure) {
					coreInitFailures.put(name, failure);
				}
			}
		}
	}

	/**
	 * @return a Collection of registered SolrCores
	 */
	public Collection<SolrCore> getCores() {
		List<SolrCore> lst = new ArrayList<SolrCore>();
		synchronized (cores) {
			lst.addAll(this.cores.values());
		}
		return lst;
	}

	/**
	 * @return a Collection of the names that cores are mapped to
	 */
	public Collection<String> getCoreNames() {
		List<String> lst = new ArrayList<String>();
		synchronized (cores) {
			lst.addAll(this.cores.keySet());
		}
		return lst;
	}

	/**
	 * This method is currently experimental.
	 * 
	 * @return a Collection of the names that a specific core is mapped to.
	 */
	public Collection<String> getCoreNames(SolrCore core) {
		List<String> lst = new ArrayList<String>();
		synchronized (cores) {
			for (Map.Entry<String, SolrCore> entry : cores.entrySet()) {
				if (core == entry.getValue()) {
					lst.add(entry.getKey());
				}
			}
		}
		return lst;
	}

	/**
	 * Returns an immutable Map of Exceptions that occured when initializing
	 * SolrCores (either at startup, or do to runtime requests to create cores)
	 * keyed off of the name (String) of the SolrCore that had the Exception
	 * during initialization.
	 * <p>
	 * While the Map returned by this method is immutable and will not change
	 * once returned to the client, the source data used to generate this Map
	 * can be changed as various SolrCore operations are performed:
	 * </p>
	 * <ul>
	 * <li>Failed attempts to create new SolrCores will add new Exceptions.</li>
	 * <li>Failed attempts to re-create a SolrCore using a name already
	 * contained in this Map will replace the Exception.</li>
	 * <li>Failed attempts to reload a SolrCore will cause an Exception to be
	 * added to this list -- even though the existing SolrCore with that name
	 * will continue to be available.</li>
	 * <li>Successful attempts to re-created a SolrCore using a name already
	 * contained in this Map will remove the Exception.</li>
	 * <li>Registering an existing SolrCore with a name already contained in
	 * this Map (ie: ALIAS or SWAP) will remove the Exception.</li>
	 * </ul>
	 */
	public Map<String, Exception> getCoreInitFailures() {
		synchronized (coreInitFailures) {
			return Collections
					.unmodifiableMap(new LinkedHashMap<String, Exception>(
							coreInitFailures));
		}
	}

	// ---------------- Core name related methods ---------------
	/**
	 * Recreates a SolrCore. While the new core is loading, requests will
	 * continue to be dispatched to and processed by the old core
	 * 
	 * @param name
	 *            the name of the SolrCore to reload
	 */
	public void reload(String name) throws ParserConfigurationException,
			IOException, SAXException {

		// :TODO: would be really nice if this method wrapped any underlying
		// errors and only threw SolrException

		Exception failure = null;
		try {

			name = checkDefault(name);
			SolrCore core;
			synchronized (cores) {
				core = cores.get(name);
			}
			if (core == null)
				throw new SolrException(SolrException.ErrorCode.BAD_REQUEST,
						"No such core: " + name);

			CoreDescriptor cd = core.getCoreDescriptor();

			File instanceDir = new File(cd.getInstanceDir());
			if (!instanceDir.isAbsolute()) {
				instanceDir = new File(getSolrHome(), cd.getInstanceDir());
			}

			log.info("Reloading SolrCore '{}' using instanceDir: {}",
					cd.getName(), instanceDir.getAbsolutePath());

			SolrResourceLoader solrLoader;
			if (zkController == null) {
				solrLoader = new SolrResourceLoader(
						instanceDir.getAbsolutePath(), libLoader, getCoreProps(
								instanceDir.getAbsolutePath(),
								cd.getPropertiesName(), cd.getCoreProperties()));
			} else {
				try {
					String collection = cd.getCloudDescriptor()
							.getCollectionName();
					zkController
							.createCollectionZkNode(cd.getCloudDescriptor());

					String zkConfigName = zkController
							.readConfigName(collection);
					if (zkConfigName == null) {
						log.error("Could not find config name for collection:"
								+ collection);
						throw new ZooKeeperException(
								SolrException.ErrorCode.SERVER_ERROR,
								"Could not find config name for collection:"
										+ collection);
					}
					solrLoader = new ZkSolrResourceLoader(
							instanceDir.getAbsolutePath(), zkConfigName,
							libLoader, getCoreProps(
									instanceDir.getAbsolutePath(),
									cd.getPropertiesName(),
									cd.getCoreProperties()), zkController);
				} catch (KeeperException e) {
					log.error("", e);
					throw new ZooKeeperException(
							SolrException.ErrorCode.SERVER_ERROR, "", e);
				} catch (InterruptedException e) {
					// Restore the interrupted status
					Thread.currentThread().interrupt();
					log.error("", e);
					throw new ZooKeeperException(
							SolrException.ErrorCode.SERVER_ERROR, "", e);
				}
			}

			SolrCore newCore = core.reload(solrLoader, core);
			// keep core to orig name link
			String origName = coreToOrigName.remove(core);
			if (origName != null) {
				coreToOrigName.put(newCore, origName);
			}
			register(name, newCore, false);

			// :TODO: Java7...
			// http://docs.oracle.com/javase/7/docs/technotes/guides/language/catch-multiple.html
		} catch (ParserConfigurationException e1) {
			failure = e1;
			throw e1;
		} catch (IOException e2) {
			failure = e2;
			throw e2;
		} catch (SAXException e3) {
			failure = e3;
			throw e3;
		} catch (RuntimeException e4) {
			failure = e4;
			throw e4;
		} finally {
			if (null != failure) {
				log.error("Unable to reload core: " + name, failure);
			}
			synchronized (coreInitFailures) {
				// remove first so insertion order is updated and newest is last
				coreInitFailures.remove(name);
				if (null != failure) {
					coreInitFailures.put(name, failure);
				}
			}
		}
	}

	private String checkDefault(String name) {
		return (null == name || name.isEmpty()) ? defaultCoreName : name;
	}

	/**
	 * Swaps two SolrCore descriptors.
	 */
	public void swap(String n0, String n1) {
		if (n0 == null || n1 == null) {
			throw new SolrException(SolrException.ErrorCode.BAD_REQUEST,
					"Can not swap unnamed cores.");
		}
		n0 = checkDefault(n0);
		n1 = checkDefault(n1);
		synchronized (cores) {
			SolrCore c0 = cores.get(n0);
			SolrCore c1 = cores.get(n1);
			if (c0 == null)
				throw new SolrException(SolrException.ErrorCode.BAD_REQUEST,
						"No such core: " + n0);
			if (c1 == null)
				throw new SolrException(SolrException.ErrorCode.BAD_REQUEST,
						"No such core: " + n1);
			cores.put(n0, c1);
			cores.put(n1, c0);

			c0.setName(n1);
			c0.getCoreDescriptor().name = n1;
			c1.setName(n0);
			c1.getCoreDescriptor().name = n0;
		}

		log.info("swapped: " + n0 + " with " + n1);
	}

	/**
	 * Removes and returns registered core w/o decrementing it's reference count
	 */
	public SolrCore remove(String name) {
		name = checkDefault(name);

		synchronized (cores) {
			SolrCore core = cores.remove(name);
			coreToOrigName.remove(core);
			return core;
		}

	}

	public void rename(String name, String toName) {
		SolrCore core = getCore(name);
		try {
			if (core != null) {
				register(toName, core, false);
				name = checkDefault(name);

				synchronized (cores) {
					cores.remove(name);
				}
			}
		} finally {
			if (core != null) {
				core.close();
			}
		}
	}

	/**
	 * Gets a core by name and increase its refcount.
	 * 
	 * @see SolrCore#close()
	 * @param name
	 *            the core name
	 * @return the core if found
	 */
	public SolrCore getCore(String name) {
		name = checkDefault(name);
		synchronized (cores) {
			SolrCore core = cores.get(name);
			if (core != null)
				core.open(); // increment the ref count while still synchronized
			return core;
		}
	}

	// ---------------- Multicore self related methods ---------------
	/**
	 * Creates a CoreAdminHandler for this MultiCore.
	 * 
	 * @return a CoreAdminHandler
	 */
	protected CoreAdminHandler createMultiCoreHandler(
			final String adminHandlerClass) {
		// :TODO: why create a new SolrResourceLoader? why not use this.loader
		// ???
		SolrResourceLoader loader = new SolrResourceLoader(solrHome, libLoader,
				null);
		return loader.newAdminHandlerInstance(CoreContainer.this,
				adminHandlerClass);
	}

	public CoreAdminHandler getMultiCoreHandler() {
		return coreAdminHandler;
	}

	public CollectionsHandler getCollectionsHandler() {
		return collectionsHandler;
	}

	/**
	 * the default core name, or null if there is no default core name
	 */
	public String getDefaultCoreName() {
		return defaultCoreName;
	}

	// all of the following properties aren't synchronized
	// but this should be OK since they normally won't be changed rapidly
	public boolean isPersistent() {
		return persistent;
	}

	public void setPersistent(boolean persistent) {
		this.persistent = persistent;
	}

	public String getAdminPath() {
		return adminPath;
	}

	public void setAdminPath(String adminPath) {
		this.adminPath = adminPath;
	}

	public String getManagementPath() {
		return managementPath;
	}

	/**
	 * Sets the alternate path for multicore handling: This is used in case
	 * there is a registered unnamed core (aka name is "") to declare an
	 * alternate way of accessing named cores. This can also be used in a pseudo
	 * single-core environment so admins can prepare a new version before
	 * swapping.
	 */
	public void setManagementPath(String path) {
		this.managementPath = path;
	}

	public LogWatcher getLogging() {
		return logging;
	}

	public void setLogging(LogWatcher v) {
		logging = v;
	}

	public File getConfigFile() {
		return configFile;
	}

	/** Persists the cores config file in cores.xml. */
	public void persist() {
		persistFile(null);
	}

	/** Persists the cores config file in a user provided file. */
	public void persistFile(File file) {
		log.info("Persisting cores config to "
				+ (file == null ? configFile : file));

		// <solr attrib="value">
		Map<String, String> rootSolrAttribs = new HashMap<String, String>();
		if (libDir != null)
			rootSolrAttribs.put("sharedLib", libDir);
		rootSolrAttribs.put("persistent", Boolean.toString(isPersistent()));

		// <solr attrib="value"> <cores attrib="value">
		Map<String, String> coresAttribs = new HashMap<String, String>();
		addCoresAttrib(coresAttribs, "adminPath", this.adminPath, null);
		addCoresAttrib(coresAttribs, "adminHandler", this.adminHandler, null);
		addCoresAttrib(coresAttribs, "shareSchema",
				Boolean.toString(this.shareSchema),
				Boolean.toString(DEFAULT_SHARE_SCHEMA));
		addCoresAttrib(coresAttribs, "host", this.host, null);

		if (!(null == defaultCoreName || defaultCoreName.equals(""))) {
			coresAttribs.put("defaultCoreName", defaultCoreName);
		}

		addCoresAttrib(coresAttribs, "hostPort", this.hostPort,
				DEFAULT_HOST_PORT);
		addCoresAttrib(coresAttribs, "zkClientTimeout",
				intToString(this.zkClientTimeout),
				Integer.toString(DEFAULT_ZK_CLIENT_TIMEOUT));
		addCoresAttrib(coresAttribs, "hostContext", this.hostContext,
				DEFAULT_HOST_CONTEXT);
		addCoresAttrib(coresAttribs, "leaderVoteWait", this.leaderVoteWait,
				LEADER_VOTE_WAIT);

		List<SolrCoreXMLDef> solrCoreXMLDefs = new ArrayList<SolrCoreXMLDef>();

		synchronized (cores) {
			for (SolrCore solrCore : cores.values()) {
				Map<String, String> coreAttribs = new HashMap<String, String>();
				CoreDescriptor dcore = solrCore.getCoreDescriptor();

				String coreName = dcore.name;
				Node coreNode = null;

				if (cfg != null) {
					NodeList nodes = (NodeList) cfg.evaluate("solr/cores/core",
							XPathConstants.NODESET);

					String origCoreName = coreToOrigName.get(solrCore);

					if (origCoreName == null) {
						origCoreName = coreName;
					}

					// look for an existing node

					// first look for an exact match
					for (int i = 0; i < nodes.getLength(); i++) {
						Node node = nodes.item(i);

						String name = DOMUtil.getAttr(node, CORE_NAME, null);
						if (origCoreName.equals(name)) {
							coreNode = node;
							if (coreName.equals(origCoreName)) {
								coreName = name;
							}
							break;
						}
					}

					if (coreNode == null) {
						// see if we match with substitution
						for (int i = 0; i < nodes.getLength(); i++) {
							Node node = nodes.item(i);
							String name = DOMUtil
									.getAttr(node, CORE_NAME, null);
							if (origCoreName.equals(DOMUtil.substituteProperty(
									name, loader.getCoreProperties()))) {
								coreNode = node;
								if (coreName.equals(origCoreName)) {
									coreName = name;
								}
								break;
							}
						}
					}
				}

				coreAttribs.put(CORE_NAME, coreName);

				String instanceDir = dcore.getInstanceDir();
				addCoreProperty(coreAttribs, coreNode, CORE_INSTDIR,
						instanceDir, null);

				// write config
				String configName = dcore.getConfigName();
				addCoreProperty(coreAttribs, coreNode, CORE_CONFIG, configName,
						dcore.getDefaultConfigName());

				// write schema
				String schema = dcore.getSchemaName();
				addCoreProperty(coreAttribs, coreNode, CORE_SCHEMA, schema,
						dcore.getDefaultSchemaName());

				String dataDir = dcore.dataDir;
				addCoreProperty(coreAttribs, coreNode, CORE_DATADIR, dataDir,
						null);

				CloudDescriptor cd = dcore.getCloudDescriptor();
				String shard = null;
				String roles = null;
				if (cd != null) {
					shard = cd.getShardId();
					roles = cd.getRoles();
				}
				addCoreProperty(coreAttribs, coreNode, CORE_SHARD, shard, null);

				addCoreProperty(coreAttribs, coreNode, CORE_ROLES, roles, null);

				String collection = null;
				// only write out the collection name if it's not the default
				// (the
				// core
				// name)
				if (cd != null) {
					collection = cd.getCollectionName();
				}

				addCoreProperty(coreAttribs, coreNode, CORE_COLLECTION,
						collection, dcore.name);

				// we don't try and preserve sys prop defs in these
				String opt = dcore.getPropertiesName();
				if (opt != null) {
					coreAttribs.put(CORE_PROPERTIES, opt);
				}

				SolrCoreXMLDef solrCoreXMLDef = new SolrCoreXMLDef();
				solrCoreXMLDef.coreAttribs = coreAttribs;
				solrCoreXMLDef.coreProperties = dcore.getCoreProperties();
				solrCoreXMLDefs.add(solrCoreXMLDef);
			}

			SolrXMLDef solrXMLDef = new SolrXMLDef();
			solrXMLDef.coresDefs = solrCoreXMLDefs;
			solrXMLDef.containerProperties = containerProperties;
			solrXMLDef.solrAttribs = rootSolrAttribs;
			solrXMLDef.coresAttribs = coresAttribs;
			solrXMLSerializer.persistFile(file == null ? configFile : file,
					solrXMLDef);
		}
	}

	private String intToString(Integer integer) {
		if (integer == null)
			return null;
		return Integer.toString(integer);
	}

	private void addCoresAttrib(Map<String, String> coresAttribs,
			String attribName, String attribValue, String defaultValue) {
		if (cfg == null) {
			coresAttribs.put(attribName, attribValue);
			return;
		}

		if (attribValue != null) {
			String rawValue = cfg.get("solr/cores/@" + attribName, null);
			if (rawValue == null && defaultValue != null
					&& attribValue.equals(defaultValue))
				return;
			if (attribValue.equals(DOMUtil.substituteProperty(rawValue,
					loader.getCoreProperties()))) {
				coresAttribs.put(attribName, rawValue);
			} else {
				coresAttribs.put(attribName, attribValue);
			}
		}
	}

	private void addCoreProperty(Map<String, String> coreAttribs, Node node,
			String name, String value, String defaultValue) {
		if (node == null) {
			coreAttribs.put(name, value);
			return;
		}

		if (node != null) {
			String rawAttribValue = DOMUtil.getAttr(node, name, null);
			if (value == null) {
				coreAttribs.put(name, rawAttribValue);
				return;
			}
			if (rawAttribValue == null && defaultValue != null
					&& value.equals(defaultValue)) {
				return;
			}
			if (rawAttribValue != null
					&& value.equals(DOMUtil.substituteProperty(rawAttribValue,
							loader.getCoreProperties()))) {
				coreAttribs.put(name, rawAttribValue);
			} else {
				coreAttribs.put(name, value);
			}
		}

	}

	public String getSolrHome() {
		return solrHome;
	}

	public boolean isZooKeeperAware() {
		return zkController != null;
	}

	public ZkController getZkController() {
		return zkController;
	}

	public boolean isShareSchema() {
		return shareSchema;
	}

	/**
	 * The default ShardHandlerFactory used to communicate with other solr
	 * instances
	 */
	public ShardHandlerFactory getShardHandlerFactory() {
		synchronized (this) {
			if (shardHandlerFactory == null) {
				Map m = new HashMap();
				m.put("class", HttpShardHandlerFactory.class.getName());
				PluginInfo info = new PluginInfo("shardHandlerFactory", m,
						null, Collections.<PluginInfo> emptyList());

				HttpShardHandlerFactory fac = new HttpShardHandlerFactory();
				fac.init(info);
				shardHandlerFactory = fac;
			}
			return shardHandlerFactory;
		}
	}

	private SolrConfig getSolrConfigFromZk(String zkConfigName,
			String solrConfigFileName, SolrResourceLoader resourceLoader)
			throws IOException, ParserConfigurationException, SAXException,
			KeeperException, InterruptedException {
		byte[] config = zkController.getConfigFileData(zkConfigName,
				solrConfigFileName);
		InputSource is = new InputSource(new ByteArrayInputStream(config));
		is.setSystemId(SystemIdResolver
				.createSystemIdFromResourceName(solrConfigFileName));
		SolrConfig cfg = solrConfigFileName == null ? new SolrConfig(
				resourceLoader, SolrConfig.DEFAULT_CONF_FILE, is)
				: new SolrConfig(resourceLoader, solrConfigFileName, is);

		return cfg;
	}

	private IndexSchema getSchemaFromZk(String zkConfigName, String schemaName,
			SolrConfig config, SolrResourceLoader resourceLoader)
			throws KeeperException, InterruptedException {
		byte[] configBytes = zkController.getConfigFileData(zkConfigName,
				schemaName);
		InputSource is = new InputSource(new ByteArrayInputStream(configBytes));
		is.setSystemId(SystemIdResolver
				.createSystemIdFromResourceName(schemaName));
		IndexSchema schema = new IndexSchema(config, schemaName, is);
		return schema;
	}

	private static final String DEF_SOLR_XML = "<?xml version=\"1.0\" encoding=\"UTF-8\" ?>\n"
			+ "<solr persistent=\"false\">\n"
			+ "  <cores adminPath=\"/admin/cores\" defaultCoreName=\""
			+ DEFAULT_DEFAULT_CORE_NAME
			+ "\">\n"
			+ "    <core name=\""
			+ DEFAULT_DEFAULT_CORE_NAME
			+ "\" shard=\"${shard:}\" instanceDir=\"collection1\" />\n"
			+ "  </cores>\n" + "</solr>";
}
